﻿#include "FITKGraphObjectConstraintCoupling.h"

// VTK
#include <vtkPoints.h>
#include <vtkVertex.h>
#include <vtkLine.h>
#include <vtkCellArray.h>
#include <vtkUnstructuredGrid.h>
#include <vtkPolyData.h>
#include <vtkPointData.h>
#include <vtkCellData.h>
#include <vtkProperty.h>
#include <vtkGlyph3D.h>
#include <vtkDistanceToCamera.h>
#include <vtkRegularPolygonSource.h>
#include <vtkExtractSelection.h>
#include <vtkSelection.h>
#include <vtkSelectionNode.h>

// Graph
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITKGraphCommons.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKPolygonSource.h"

// Global data
#include "FITK_Kernel/FITKCore/FITKDataRepo.h"

// Data
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaConstraintCoupling.h"
#include "FITK_Interface/FITKInterfacePhysics/FITKBoundaryLocation.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAssInstanceTR.h"
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaRefPoint.h"
#include "FITK_Interface/FITKInterfaceModel/FITKModelSet.h"

// Data manager
#include "FITKSetSurfPolyProvider.h"

namespace Exchange
{
    FITKGraphObjectConstraintCoupling::FITKGraphObjectConstraintCoupling(AbaqusData::FITKDataCase* caseData, Interface::FITKAbaConstraintCoupling* couplingData)
        : FITKGraphObjectInteractionBase(caseData, couplingData)
    {
        if (!couplingData)
        {
            return;
        }

        // Initialize.
        init();

        // Generate the poly data for creating coupling.
        bool flag = generatePolyData(couplingData);
        if (!flag)
        {
            return;
        }

        // Generate coupling actor.
        generateGraph();

        // Update the visibility.
        updateVisibility();
    }

    FITKGraphObjectConstraintCoupling::~FITKGraphObjectConstraintCoupling()
    {
        // Delete extractor for location 1.
        deleteVtkObj(m_selectNodeLoc_1);
        deleteVtkObj(m_sectionLoc_1);
        deleteVtkObj(m_extractSelectionLoc_1);
        deleteVtkObj(m_idArrayLoc_1);
    }

    void FITKGraphObjectConstraintCoupling::setVisible(bool visibility)
    {
        this->Superclass::setVisible(visibility);

        // The coupling has an additional actor for showing the control points.
        if (m_fActorPointCircle)
        {
            m_fActorPointCircle->SetVisibility(visibility);
        }
    }

    void FITKGraphObjectConstraintCoupling::highlight(QColor color)
    {
        // If the object is invisible, then return.
        if (!getDataVisibility())
        {
            return;
        }

        // Set the color to control points' highlight color.
        double color3[3] = { 0., 0., 0. };
        if (color.isValid())
        {
            FITKGraphCommons::QColorToDouble3(color, color3);
        }
        else
        {
            FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_constraintHighlightColor, color3);
        }

        if (m_fActorPointCircle)
        {
            m_fActorPointCircle->GetProperty()->SetColor(color3);
        }

        this->Superclass::highlight();
    }

    void FITKGraphObjectConstraintCoupling::disHighlight()
    {
        if (m_fActorPointCircle)
        {
            // Set the color to control points' color.
            double color3[3] = { 0., 0., 0. };
            FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_constCtrlPointColor, color3);
            m_fActorPointCircle->GetProperty()->SetColor(color3);
        }

        this->Superclass::disHighlight();
    }

    void FITKGraphObjectConstraintCoupling::simplified(bool enabled)
    {
        if (!m_graphSimplified)
        {
            return;
        }

        bool dataVisible = getDataVisibility();

        // Change the actor visibility while changing the camera.
        if (dataVisible)
        {
            if (m_fActor)
            {
                m_fActor->SetVisibility(!enabled);
            }

            if (m_fActorPointCircle)
            {
                m_fActorPointCircle->SetVisibility(!enabled);
            }
        }
    }

    void FITKGraphObjectConstraintCoupling::updateVisibility()
    {
        // Update the full actor.
        FITKGraphObject3D::updateVisibility();

        if (!m_idArray || !m_idArrayLoc_1 || !m_modelIdArray || !m_ugrid)
        {
            return;
        }

        QHash<int, bool> modelVisibilityHash;

        // Save the visibility first, and check if the visbility has changed at the same time.
        bool hasChanged = false;
        for (int modelId : m_modelIds_1 + m_modelIds_2)
        {
            bool parentVisible = getParentDataVisibility(modelId);
            bool oldVisibility = false;
            if (m_modelVisibilityHash.contains(modelId))
            {
                oldVisibility = m_modelVisibilityHash[modelId];
            }

            if (parentVisible != oldVisibility)
            {
                hasChanged = true;
            }

            if (!modelVisibilityHash.contains(modelId))
            {
                modelVisibilityHash.insert(modelId, parentVisible);
            }
        }

        // If the visibility hasn't changed, then return.
        if (!hasChanged && !m_modelVisibilityHash.isEmpty())
        {
            return;
        }

        // Reset the extractor's ids.
        resetVtkObj(m_idArray);
        resetVtkObj(m_idArrayLoc_1);

        // Update the extractor's ids.
        for (int i = 0; i < m_ugrid->GetNumberOfCells(); i++)
        {
            bool visibility = getCellVisibilityWithModel(m_ugrid->GetCell(i), m_modelIdArray, modelVisibilityHash);
            if (visibility)
            {
                m_idArray->InsertNextValue(i);
            }
        }

        // Update the location 1 visibility.
        vtkIntArray* modelIdArrayLoc1 = vtkIntArray::SafeDownCast(m_gridLoc_1->GetCellData()->GetArray(FITKSetSurfPolyProvider::getInstance()->getModelIdsArrayName()));
        if (modelIdArrayLoc1)
        {
            for (int i = 0; i < modelIdArrayLoc1->GetNumberOfValues(); i++)
            {
                int modelId = modelIdArrayLoc1->GetValue(i);
                if (modelVisibilityHash[modelId])
                {
                    m_idArrayLoc_1->InsertNextValue(i);
                }
            }
        }

        // Update the data.
        m_ugrid->Modified();
        m_gridLoc_1->Modified();

        // Save the flags.
        m_modelVisibilityHash = modelVisibilityHash;
    }

    void FITKGraphObjectConstraintCoupling::initActors()
    {
        this->Superclass::initActors();

        // Initialize the extractor for location 1.
        //@{
        m_selectNodeLoc_1 = vtkSelectionNode::New();
        m_sectionLoc_1 = vtkSelection::New();
        m_extractSelectionLoc_1 = vtkExtractSelection::New();
        m_selectNodeLoc_1->SetContentType(vtkSelectionNode::INDICES);
        m_selectNodeLoc_1->SetFieldType(vtkSelectionNode::SelectionField::CELL);
        m_sectionLoc_1->AddNode(m_selectNodeLoc_1);
        m_extractSelectionLoc_1->SetInputData(1, m_sectionLoc_1);

        m_idArrayLoc_1 = vtkIdTypeArray::New();
        m_selectNodeLoc_1->SetSelectionList(m_idArrayLoc_1);
        m_extractSelectionLoc_1->SetInputData(m_gridLoc_1);
        //@}

        // Initialize control points circle actor.
        m_fActorPointCircle = FITKGraphActor::New();
        m_fActorPointCircle->SetPickable(false);
        m_fActorPointCircle->setGraphObject(this);
        m_fActorPointCircle->setScalarVisibility(false);

        // Set the color to control points' color.
        double color3[3] = { 0., 0., 0. };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_constCtrlPointColor, color3);
        m_fActorPointCircle->GetProperty()->SetColor(color3);

        // Create the data source.
        FITKPolygonSource* source = FITKPolygonSource::New();
        source->SetGeneratePolygon(false);
        source->SetGeneratePolyline(true);
        source->SetRadius(1);
        source->SetNumberOfSides(10);
        m_polygonSource = source;

        // For none-fixed size.
        //@{
        vtkGlyph3D* glyph = vtkGlyph3D::New();
        vtkDistanceToCamera* distanceToCamera = vtkDistanceToCamera::New();
        distanceToCamera->SetScreenSize(m_glyphSize);

        // Set the input.( The location 1 is the control points' set. )
        distanceToCamera->SetInputConnection(m_extractSelectionLoc_1->GetOutputPort());

        // Add the point array name need to be used.             
#if VTK_MAJOR_VERSION < 8
        glyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "DistanceToCamera");
#else
        glyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, distanceToCamera->GetDistanceArrayName());
#endif

        glyph->SetInputConnection(distanceToCamera->GetOutputPort());
        glyph->SetScaling(true);
        glyph->SetScaleModeToScaleByScalar();
        glyph->SetVectorModeToVectorRotationOff();

        // Add to data list.
        m_glyphList.push_back(glyph);
        m_distToCameraList.push_back(distanceToCamera);

        // Set the soure data and input data.
        glyph->SetSourceConnection(m_polygonSource->GetOutputPort());
        //@}

        m_fActorPointCircle->setInputConnection(glyph->GetOutputPort());

        addActor(m_fActorPointCircle);
    }

    bool FITKGraphObjectConstraintCoupling::generatePolyData(Interface::FITKInteractionLocation* constraint)
    {
        // Get the location id.
        Interface::FITKBoundaryLocation* location1 = constraint->getFirstLocation();
        Interface::FITKBoundaryLocation* location2 = constraint->getSecondLocation();
        if (!location1 || !location2)
        {
            return false;
        }

        // Get the surface id.
        m_locationCompId_1 = location1->getComponentID();
        m_locationCompId_2 = location2->getComponentID();

        // Try to get set and surface poly data.
        QList<int> modelIds_1, modelIds_2;
        QList<int> subSetIds_1, subSetIds_2;

        // Generate locations grid.
        generateComponentGridData(m_gridLoc_1, location1, modelIds_1, subSetIds_1);
        generateComponentGridData(m_gridLoc_2, location2, modelIds_2, subSetIds_2);

        // Save the model ids.
        m_modelIds_1 << modelIds_1;
        m_modelIds_2 << modelIds_2;

        // Save the sub set ids.
        m_subSetIds_1 << subSetIds_1;
        m_subSetIds_2 << subSetIds_2;

        // The number of points and cells' center.( For adding cells )
        int nSetPt = m_gridLoc_1->GetNumberOfPoints();
        int nSurfaceCell = m_gridLoc_2->GetNumberOfCells();

        // Add set's points.
        //@{
        // Copy a new one for actor.
        // m_gridLoc1->DeepCopy(setGridloc1);

        // Get the model id array of loc 1.
        vtkIntArray* modelIdArray_1 = vtkIntArray::SafeDownCast(m_gridLoc_1->GetCellData()->GetArray(FITKSetSurfPolyProvider::getInstance()->getModelIdsArrayName()));

        for (int i = 0; i < nSetPt; i++)
        {
            // For creating lines.
            m_points->InsertNextPoint(m_gridLoc_1->GetPoint(i));

            // Add the model id in loc 1.
            int modelId = -1;
            if (modelIdArray_1)
            {
                modelId = modelIdArray_1->GetValue(i);
            }

            m_modelIdArray->InsertNextValue(modelId);
        }
        //@}

        // Add cells.
        // Get the model id array of loc 2.
        vtkIntArray* modelIdArray_2 = vtkIntArray::SafeDownCast(m_gridLoc_2->GetCellData()->GetArray(FITKSetSurfPolyProvider::getInstance()->getModelIdsArrayName()));

        for (int i = 0; i < nSurfaceCell; i++)
        {
            // Add the cell center.
            double pt[3]{ 0., 0., 0. };
            FITKGraphCommons::GetCellCenter(m_gridLoc_2->GetCell(i), pt);
            m_points->InsertNextPoint(pt);

            // Add the model id in loc 2.
            int modelId = -1;
            if (modelIdArray_2)
            {
                modelId = modelIdArray_2->GetValue(i);
            }

            m_modelIdArray->InsertNextValue(modelId);

            // Add a line cell between set( set or ref-node ) points and surface points.
            for (int j = 0; j < nSetPt; j++)
            {
                vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
                cell->InsertNextId(i + nSetPt); // The set's point is in the front of the surface's center points.
                cell->InsertNextId(j);
                m_ugrid->InsertNextCell(VTKCellType::VTK_LINE, cell);
            }
        }

        return true;
    }

    void FITKGraphObjectConstraintCoupling::generateGraph()
    {
        if (!m_ugrid)
        {
            return;
        }

        // Initialize the actors.
        initActors();

        m_fActor->setInputConnection(m_extractSelection->GetOutputPort());
    }
}   // namespace Exchange